package ua.sumdu.LukyanovChalyi.Server;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.net.Socket;
import java.util.Observable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import ua.sumdu.LukyanovChalyi.Utils.*;

import org.apache.log4j.xml.*;

public class ClientConnection extends Observable implements Runnable {

    private final String ADD_BOOK = "Add book to library!";
    private final String REMOVE_BOOK = "Remove book from library";
    private final String SET_ACTIVE_MESSAGE = "Successfull!";
    private final String READ_FROM_FILE_SUCCESSFULL = "Read books from file!";
    private final String READ_FROM_FILE_ERROR = "Can't read books from file!";
    private final String WRITE_BOOKS_TO_FILE = "Write books to file!";
    private Server server;
    private Socket socket;
    private Model model;
    private InputStream in;
    private OutputStream out;
    private ViewServer viewServer;

    public ClientConnection(Server server, Socket socket, Model model, ViewServer viewServer) {
        DOMConfigurator.configure("log4j.xml");
        this.server = server;
        this.socket = socket;
        this.model = model;
        this.viewServer = viewServer;
    }

    @Override
    public void run() {
        try {
            in = socket.getInputStream();
            out = socket.getOutputStream();
            this.addObserver(server);
            while (!Thread.interrupted()) {
                parseXMLFromClient();
            }
        } catch (TransformerException ex) {
            viewServer.showException("Error while transform xml to stream ", ex);
        } catch (ParserConfigurationException e) {
            viewServer.showException("Serious configuration error ", e);
        } catch (SAXException e) {
            viewServer.showException("Error while parsing file ", e);
        } catch (IOException e) {
            viewServer.showException("Client disconnected", e);
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException ex) {
                    viewServer.showException("Error while closing socket", ex);
                }
            }
            server.disconnect(this);
        }
    }

    public void parseXMLFromClient() throws IOException, ParserConfigurationException, SAXException, TransformerException {
        try {
            XMLUtils utils = new XMLUtils();
            Document doc = utils.getDocument(in);
            Element root = doc.getDocumentElement();

            String command = root.getElementsByTagName(Transport.COMMAND).item(0).getFirstChild().getNodeValue();

            if (Transport.ADD.equals(command)) {

                CopyBook book = utils.readCopyBook((Element) root.getElementsByTagName(XMLUtils.COPY_BOOK).item(0));
                model.addCopyBook(book);
                setChanged();
                notifyObservers(new Object[]{Transport.SHOW_MESSAGE, ADD_BOOK});

            } else if (Transport.REMOVE_BY_ID.equals(command)) {

                long id = Long.parseLong(root.getElementsByTagName(XMLUtils.ID).item(0).getFirstChild().getNodeValue());
                model.removeCopyBookByID(id);
                createXMLToClient(new Object[]{Transport.SHOW_MESSAGE, REMOVE_BOOK});

            } else if (Transport.SET_ACTIVE_BY_ID.equals(command)) {

                long id = Long.parseLong(root.getElementsByTagName(Transport.ID).item(0).getFirstChild().getNodeValue());
                boolean active =
                        Boolean.parseBoolean(root.getElementsByTagName(Transport.SET_ACTIVE).item(0).getFirstChild().getNodeValue());
                model.setActive(id, active);
                createXMLToClient(new Object[]{Transport.SHOW_MESSAGE, SET_ACTIVE_MESSAGE});

            } else if (Transport.SHOW_BOOKS.equals(command)) {

                createXMLToClient(new Object[]{command, model.getBooks()});

            } else if (Transport.WRITE_TO_FILE.equals(command)) {

                ReadWriteFiles files = null;
                String filename = root.getElementsByTagName(Transport.FILENAME).item(0).getFirstChild().getNodeValue();
                File file = new File(filename);
                if (!file.exists()) {
                    file.createNewFile();
                }
                if (filename.endsWith(".xml")) {
                    files = new ReadWriteXMLFile();
                } else if (filename.endsWith(".txt")) {
                    files = new ReadWriteTextFile();
                }
                files.write(model.getBooks(), filename);
                createXMLToClient(new Object[]{Transport.SHOW_MESSAGE, WRITE_BOOKS_TO_FILE});
            } else if (Transport.READ_FROM_FILE.equals(command)) {
                String filename = root.getElementsByTagName(Transport.FILENAME).item(0).getFirstChild().getNodeValue();
                if ((new File(filename)).exists()) {
                    ReadWriteFiles files = null;
                    if (filename.endsWith(".xml")) {
                        files = new ReadWriteXMLFile();
                    } else if (filename.endsWith(".txt")) {
                        files = new ReadWriteTextFile();
                    }
                    Map<Long, CopyBook> map = files.read(filename);
                    if (map != null) {
                        model.addAllBooks(map);
                        createXMLToClient(new Object[]{Transport.SHOW_MESSAGE, READ_FROM_FILE_SUCCESSFULL});
                    } else {
                        createXMLToClient(new Object[]{Transport.SHOW_MESSAGE, READ_FROM_FILE_ERROR});
                    }
                } else {
                    throw new ModelException("Can't read books from file!");
                }
            }
        } catch (ModelException e) {
            try {
                createXMLToClient(new Object[]{Transport.SHOW_MESSAGE, e.getMessage()});
                viewServer.showException(e.getMessage(), e.getCause());
            } catch (IOException ex) {
                viewServer.showException("Eror while sending answer to client ", ex);
            } catch (TransformerException ex) {
                viewServer.showException("Error while transform xml to stream ", ex);
            } catch (ParserConfigurationException e1) {
                viewServer.showException("Serious configuration error ", e1);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void createXMLToClient(Object... param) throws ParserConfigurationException, IOException, TransformerException {
        XMLUtils utils = new XMLUtils();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.newDocument();

        Element root = doc.createElement(XMLUtils.LIBRARY);
        Element command = doc.createElement(Transport.COMMAND);
        Element node = null;

        String cmnd = (String) param[0];
        command.appendChild(doc.createTextNode(cmnd));
        root.appendChild(command);

        if (Transport.SHOW_BOOKS.equals(cmnd)) {
            Map<Long, CopyBook> map = (Map<Long, CopyBook>) param[1];
            Set<Long> keys = map.keySet();
            for (Long key : keys) {
                root.appendChild(utils.makeCopyBook(doc, map.get(key)));
            }
        } else if (Transport.SHOW_MESSAGE.equals(cmnd)) {
            node = doc.createElement(Transport.MESSAGE);
            node.appendChild(doc.createTextNode(String.valueOf(param[1])));
        }

        if (node != null) {
            root.appendChild(node);
        }
        doc.appendChild(root);

        utils.writeDocumentToStream(doc, out);

    }
}
